Skip to content

Embed LDH's RDF-defined blocks as atomic RDFa islands#16

Merged
namedgraph merged 6 commits into
masterfrom
feature/ldh-object-blocks
Jul 15, 2026
Merged

Embed LDH's RDF-defined blocks as atomic RDFa islands#16
namedgraph merged 6 commits into
masterfrom
feature/ldh-object-blocks

Conversation

@namedgraph

@namedgraph namedgraph commented Jul 13, 2026

Copy link
Copy Markdown
Member

Summary

Blocks defined purely in RDF — ldh:View, ldh:ResultSetChart — can now be embedded wherever the content model admits a block, per the LDH v6 document format: a div[@about][@typeof] element with its definition triples as span[@property] children (objects via @resource, literals as span text). Embedding an existing resource needs no node at all: a reference block is an empty div whose @about is the resource's own absolute URI (the demo embeds http://dbpedia.org/resource/Ada_Lovelace directly — the v5 ldh:Object + rdf:value indirection is retired, per the updated wiki spec). No wrappers, no rdf:_N sequences, no document→block containment edges — extraction yields the block descriptions (a reference block extracts to zero triples), containment is implicit in the content.

The editor treats such a block as an atomic island:

  • never a text host — the placeholder and its spans are locked, tabindex="-1" makes it a focusable navigation island (shared doctrine with block images: arrow-key selection, Backspace/Delete removes it whole, undo/redo chords work while it's focused);
  • a hard boundary for Backspace merges (local:merge-host-in gains a tail-island guard) and for the cross-host delete machine (local:clamped-range moves boundaries out of islands, so a selection takes them whole or not at all);
  • byte-identical round-trip: the visual rendering is injected into an ephemeral div[data-role=rendering] child by the overridable mode="local:render-island" hook and stripped at canonicalization; the extractor skips it.

Core / extension split

  • src/blocks.xsl (core, zero LDH knowledge): $object-block-types param (default empty), the local:island() predicate (typed islands OR local:reference-block() — an effectively-empty div with an absolute non-document @about), the render hook + neutral card, and extension stubs for dialogs / toolbar buttons / slash-menu items.
  • src/ldh-blocks.xsl + entry src/ldh-editor.xsl (xsl:import layering, higher import precedence): the LDH class list, async renderers — ixsl:promise/ixsl:http-request chains fetching the referenced documents by content negotiation on clean trailing-slash URIs (no file extensions anywhere) — and the "Block…" insert dialog wired into the slash menu and toolbar. In production LinkedDataHub, client.xsl plays this module's role, bridging the hook into its block rendering (v6 ldh:Block / v5 ldh:RenderRow — MIGRATION.md §12).

Build & deployment

Two SEFs: the core dist/index.xsl.sef.json is what GitHub Pages deploys (Pages can't content-negotiate); the LDH-extended dist/ldh-editor.xsl.sef.json powers the local /demo/ showcase and the browser fixtures. make up now serves via node serve.mjs, which negotiates application/rdf+xml / application/sparql-results+xml representations of document URIs.

Test plan

  • make test — 50 headless PASS, incl. new extractor/canonical/lint fixtures for the storage form
  • make test-browser — all 14 suites green, incl. new blocks.mjs (43 assertions: hydration, view-source round-trip, island navigation/deletion/boundaries, dialog insertion top-level and nested in a list item, stage-2/sweep delete atomicity, canonical copy, undo + invariants after every mutation)
  • fixture-nesting.html runs the extended SEF, so every existing suite doubles as proof the extension layers without disturbing core behavior
  • Manual: make up/demo/ hydrates a chart over conneg and a nested reference block live from DBpedia (CORS + 303 to RDF/XML); Source shows the clean v6 placeholders; Extract RDF shows the block descriptions

🤖 Generated with Claude Code

namedgraph and others added 6 commits July 13, 2026 11:24
Blocks defined purely in RDF (ldh:View, ldh:ResultSetChart, ldh:Object)
can now live wherever the content model admits a div, per the LDH v6
document format: a div[@about][@typeof] block element with its
definition triples as span[@Property] children (objects via @resource,
literals as span text) - no wrappers, no rdf:_N sequences, no
document-to-block containment edges. The editor treats such a div as an
atomic island: never a text host, focusable/navigable/deletable like a
block image (shared local:select-island doctrine, undo/redo chords
included), a hard boundary for merges (merge-host-in gains a
tail-island guard - islands hold no hosts, the lookup would leapfrog
them) and for the delete machine (clamped-range moves boundaries out of
islands, so they join selections whole and are only ever removed
whole), and byte-identical on round-trip: the visual rendering is
injected into an ephemeral div[data-role=rendering] child by the
overridable mode="local:render-island" hook and stripped at
canonicalization.

The core (new src/blocks.xsl: $object-block-types param, the
local:island predicate, the render hook + neutral card, extension
stubs) knows no LDH vocabulary. The LDH extension (src/ldh-blocks.xsl,
layered by the src/ldh-editor.xsl entry via xsl:import) supplies the
class list, async renderers - ixsl:promise/ixsl:http-request chains
fetching the referenced documents by content negotiation on clean
trailing-slash URIs (no file extensions anywhere; serve.mjs replaces
python http.server and negotiates rdf+xml / sparql-results+xml
representations) - and the Block... insert dialog wired into the slash
menu and toolbar through the new hook points. In production
LinkedDataHub, client.xsl plays the extension's role, bridging the hook
into its block rendering (MIGRATION.md par. 12).

Two build flavors: the core SEF (dist/index.xsl.sef.json) is what
GitHub Pages deploys - Pages cannot content-negotiate, so the
LDH-extended SEF (dist/ldh-editor.xsl.sef.json) and the demo/ showcase
page stay local-only. New coverage: extractor/canonical/lint fixtures
for the storage form, tests/browser/blocks.mjs against
tests/fixture-blocks.html (hydration, round-trip, navigation,
boundaries, dialog insertion top-level and nested, stage-2/sweep
atomicity, canonical copy, undo); fixture-nesting.html runs the
extended SEF so every core suite doubles as proof the extension layers
non-invasively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The plan shipped in 0496acf (src/blocks.xsl, src/ldh-blocks.xsl,
src/ldh-editor.xsl, demo/, tests, CLAUDE.md docs); the file is no
longer needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…blocks

# Conflicts:
#	CLAUDE.md
#	tests/browser/run.mjs
…blocks

Reconciles the object-block islands with master's nested drag-and-drop,
exclusive-c14n Source view and output downloads:

- undo.xsl: restore-snapshot re-converges chrome via local:ensure-chrome
  (nested handles included), then re-fires the island renderer for islands
  captured mid-render.
- Nested islands are draggable blocks and carry their own handles now;
  blocks.mjs invariants and expectations updated to the draggable-block
  chrome model (ported from invariants.mjs I3).
- demo/index.html and tests/fixture-blocks.html load lib/xml-c14n-sync.js,
  required by the c14n Source view.
- run.mjs runs both new suites (dragnest + blocks); CLAUDE.md weaves both
  sides' feature descriptions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v6 document format retires the v5 ldh:Object + rdf:value indirection, and
in XHTML+RDFa the last reason for it is gone: placement is structural, so
embedding an existing resource needs no node at all. A reference block is an
empty div whose @about is the referenced resource's own absolute URI - naming
the resource IS the reference, and the empty div extracts to zero triples (no
scaffolding statements in the content graph).

- blocks.xsl: local:reference-block (effectively-empty div, absolute @about
  naming a different document than the page - string tests only, safe in the
  hot island predicate), OR-ed into local:island; neutral card labels
  typeless islands 'Resource'.
- ldh-blocks.xsl: ldh:Object dropped from $object-block-types; the reference
  renderer dereferences @about by conneg (DBpedia works directly: CORS + 303
  to RDF/XML; label picks no-lang/en; property table capped at 10); dialog
  kinds are now Resource (one absolute URI straight into @about, gated by
  local:is-absolute-iri) / View / Chart with per-kind fragment id; literal
  definition spans no longer dropped on insert (filter checked @content,
  values live in text content per the v6 format).
- demo embeds Ada Lovelace by her DBpedia URI, hydrated live in the browser.
- fixture-blocks.html gains a third island (a stored reference block, its
  absolute @about stamped by the fixture script); blocks.mjs covers reference
  init/round-trip/copy and inserts a nested reference via the dialog.
- MIGRATION.md par. 12 and the wiki page define the idiom; the ac:mode
  trade-off is documented (per-embed modes need a typed block).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Islands embed external objects - their rendered cards paint real tables
(chart results, a reference's property card), and the drop resolver's cell
shortcut had no ephemera guard: pointing a dragged block at a rendered td
resolved the drop INTO the rendering div, where the next canonicalization
would silently delete it. Only content cells qualify now - a td inside
ephemera or an island interior is external rendering, never a drop zone;
the pointer falls through to the island itself, so drops land before/after
it and islands are never drop receivers.

blocks.mjs adds a real-gesture regression test (verified failing against the
unguarded resolver): dragging a paragraph onto the chart's rendered table
cell lands it adjacent to the island, never inside.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@namedgraph namedgraph merged commit 0e4267e into master Jul 15, 2026
2 checks passed
@namedgraph namedgraph deleted the feature/ldh-object-blocks branch July 15, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant